﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace NHDay_Linq.Dto.Generator
{
    class DtoExpressionVisitor : ExpressionVisitor
    {
        private ParameterExpression parameter;
        public Expression ReplaceDtoBinding(LambdaExpression exp)
        {
            parameter = exp.Parameters[0];
            return this.Visit(exp);
        }

        protected override System.Linq.Expressions.MemberBinding VisitBinding(System.Linq.Expressions.MemberBinding binding)
        {
            if (binding is MemberAssignment)
            {
                MemberAssignment assign = (MemberAssignment)binding;
                if (assign.Expression is MethodCallExpression)
                {
                    MethodCallExpression callex = (MethodCallExpression)assign.Expression;
                    if (callex.Arguments.Count == 1)
                    {
                        MemberExpression argument = (MemberExpression)callex.Arguments[0];
                        Type assembler = callex.Method.DeclaringType;
                        if (assembler != null)
                        {
                            //The object is a dto and it is assemblable.
                            FieldInfo expSelector = assembler.GetField("ExpressionSelector", BindingFlags.Static | BindingFlags.Public);
                            if (expSelector != null)
                            {
                                //var test2 = OrdersTestDto.Assembler.Expression2Selector;
                                //MemberInitExpression expr2 = (MemberInitExpression)test2.Body;
                                //MemberAssignment bnd2 = (MemberAssignment)expr2.Bindings[2];

                                LambdaExpression lambda = (LambdaExpression)expSelector.GetValue(null);
                                MemberInitExpression minit = (MemberInitExpression)lambda.Body;
                                List<MemberBinding> bindings = new List<MemberBinding>();
                                foreach (MemberBinding oribind in minit.Bindings)
                                {
                                    //1° step. access obj.Property
                                    MemberExpression accessOriObj = Expression.Property(parameter, argument.Member.Name);
                                    //2° step. access obj.Property.p'roperty
                                    MemberExpression accessObjProperty = Expression.Property(accessOriObj, oribind.Member.Name);
                                    //3° recreate the binding and store into a list
                                    MemberBinding newPropertyBind = Expression.Bind(oribind.Member, accessObjProperty);
                                    bindings.Add(newPropertyBind);
                                }
                                NewExpression constructor = Expression.New(((PropertyInfo)assign.Member).PropertyType);
                                MemberInitExpression newInitExpression = Expression.MemberInit(constructor, bindings.ToArray());
                                MemberBinding newBindingExpression = Expression.Bind(assign.Member, newInitExpression);
                                return newBindingExpression;
                            }
                        }
                    }
                }
            }
            return base.VisitBinding(binding);
        }

        protected override Expression VisitParameter(ParameterExpression p)
        {
            return base.VisitParameter(p);
        }
    }
}
